import java.nio.ByteBuffer;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;


public class Utility
{
	// encode and decode data with size great than the block using RSA cipher
	static public byte[] blockCipher(byte[] bytes, int mode, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException{
		// string initialize 2 buffers.
		// scrambled will hold intermediate results
		byte[] scrambled = new byte[0];

		// toReturn will hold the total result
		byte[] toReturn = new byte[0];
		// if we encrypt we use 100 byte long blocks. Decryption requires 128 byte long blocks (because of RSA)
		int length = (mode == Cipher.ENCRYPT_MODE)? 100 : 128;  

		// another buffer. this one will hold the bytes that have to be modified in this step
		byte[] buffer = new byte[length];

		for (int i=0; i< bytes.length; i++){

			// if we filled our buffer array we have our block ready for de- or encryption
			if ((i > 0) && (i % length == 0)){
				//execute the operation
				scrambled = cipher.doFinal(buffer);
				// add the result to our total result.
				toReturn = append(toReturn,scrambled);
				// here we calculate the length of the next buffer required
				int newlength = length;

				// if newlength would be longer than remaining bytes in the bytes array we shorten it.
				if (i + length > bytes.length) {
					 newlength = bytes.length - i;
				}
				// clean the buffer array
				buffer = new byte[newlength];
			}
			// copy byte into our buffer.
			buffer[i%length] = bytes[i];
		}

		// this step is needed if we had a trailing buffer. should only happen when encrypting.
		// example: we encrypt 110 bytes. 100 bytes per run means we "forgot" the last 10 bytes. they are in the buffer array
		scrambled = cipher.doFinal(buffer);

		// final step before we can return the modified data.
		toReturn = append(toReturn,scrambled);

		return toReturn;
	}
	
	
	// append two array
	static private byte[] append(byte[] prefix, byte[] suffix){
		byte[] toReturn = new byte[prefix.length + suffix.length];
		for (int i=0; i< prefix.length; i++){
			toReturn[i] = prefix[i];
		}
		for (int i=0; i< suffix.length; i++){
			toReturn[i+prefix.length] = suffix[i];
		}
		return toReturn;
	}
	
	// convert hex string to Byte Array
	public static byte[] hexStringToByteArray(String s) {
	    int len = s.length();
	    byte[] data = new byte[len / 2];
	    for (int i = 0; i < len; i += 2) {
	        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
	                             + Character.digit(s.charAt(i+1), 16));
	    }
	    return data;
	}
	
	// convert byte array to hex string
	 public static String byteArrayToHexString(byte[] b) {
		    StringBuffer sb = new StringBuffer(b.length * 2);
		    for (int i = 0; i < b.length; i++) {
		      int v = b[i] & 0xff;
		      if (v < 16) {
		        sb.append('0');
		      }
		      sb.append(Integer.toHexString(v));
		    }
		    return sb.toString().toUpperCase();
		  }
	 
	 //encrypt the message type
	  static public byte[] createCipheredMsgType(byte type, Cipher cipher) 
	    {
	    	 byte[] tmp = new byte[9];
	      	 tmp[0] = type;
	      	 long r1 = new Random().nextLong();
	      	 byte[] rand1 = ByteBuffer.allocate(8).putLong(r1).array();
	      	 System.arraycopy(rand1, 0, tmp, 1, 8);
	      	 try
			{
				byte[] cType = cipher.doFinal(tmp);
				return cType;
			} catch (Exception e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
	      	 
	      	return null; 	 
	    }
	    
	  static  public byte[] combineTwoByteArray(byte[] a, byte[] b)
	    {
	    	byte[] c = new byte[a.length + b.length];
	    	System.arraycopy(a, 0, c, 0, a.length);
	    	System.arraycopy(b, 0, c, a.length, b.length);
	    	return c;
	    }
	  
	  // insert the signature to the data
	  static public void insertSignature(byte[] data, int length, Cipher cipher)
	  {
		  byte[] rData = new byte[length];
		  System.arraycopy(data, 0, rData, 0, length);
		  try {
			byte[] signature = blockCipher(rData, Cipher.ENCRYPT_MODE, cipher);
			int slength = signature.length;
			byte[] l = ByteBuffer.allocate(4).putInt(slength).array();
	      	System.arraycopy(l, 0, data, length, 4);
			System.arraycopy(signature, 0, data, length + 4,slength);
			
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		  
	  }
	  
	  // validate the signature
	  static public boolean checkSignature(byte[] data, int length, Cipher cipher)
	  {
		  int dataLength = data.length;
		  
		  if(length + 4 > dataLength)
		  {
			  System.out.println("checkSignature exceed buffer size");
			  return false;
		  }
		  
		  byte[] sigLength = new byte[4];
	      System.arraycopy(data, length, sigLength, 0, 4);
	      
	      
	      ByteBuffer bb = ByteBuffer.wrap(sigLength, 0, 4);
		  int sLength = bb.getInt();
	      
		  if(length + sLength > dataLength)
		  {
			  System.out.println("checkSignature exceed buffer size");
			  return false;
		  }
		  
		  byte[] signature = new byte[sLength];
		  System.arraycopy(data, length + 4, signature, 0, sLength);
		  try {
			  byte[] deciphedData = blockCipher(signature, Cipher.DECRYPT_MODE, cipher);
			  if(deciphedData.length <  length)
			  {
				  return false;
			  }
			  
			  for(int i = 0; i < length; i++)
			  {
				  if(data[i] != deciphedData[i])
					  return false;
			  }
			  
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		  
		return true;
	  }
}
